package com.sos.core.cards
{
	import com.sos.Shared;
	import com.sos.comm.Call;
	import com.sos.comm.CallErrorEvent;
	import com.sos.comm.CallEvent;
	import com.sos.comm.CallFactory;
	import com.sos.comm.CallFailEvent;
	import com.sos.comm.CallSuccessEvent;
	import com.sos.logging.Logger;
	import com.sos.utils.JsonUtils;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;

	/**
	 *  
	 * @author ultima
	 * 
	 */	
	public class CardManager implements IEventDispatcher
	{
		
		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------
		
		// JSON data property definition
		protected static const 			JSON_PROPERTY_CARD_LIST:String			= "cardList";
		
		
		// -------------------------------------------------------------------------------
		// STATIC
		// -------------------------------------------------------------------------------
		
		// Singleton instance
		private static const _instance:CardManager = new CardManager();
		
		/**
		 * Returns the singleton instance 
		 * @return 
		 */		
		public static function getCardManager():CardManager
		{
			return _instance;
		}
		
		// -------------------------------------------------------------------------------
		// FIELDS
		// -------------------------------------------------------------------------------
		
		private var _cards:Vector.<Card>;
		private var _call:Call;
		private var _dispatcher:EventDispatcher;
		
		// -------------------------------------------------------------------------------
		// CONSTRUCTOR
		// -------------------------------------------------------------------------------
		
		public function CardManager()
		{
			// Singleton instance check
			if(_instance != null) throw new Error( "CardsManager singleton can't be created more than once!");
			// Obejct init
			_cards		= new Vector.<Card>();
			_call		= new Call(CallFactory.CALL_CARD_MANAGER_LOAD, true);
			_dispatcher = new EventDispatcher(this);
			// Event handlers setup
			_call.addEventListener(CallEvent.EVENT_CALL_SUCCESS, 	callSuccesHandler);
			_call.addEventListener(CallEvent.EVENT_CALL_ERROR, 		callErrorHandler);
			_call.addEventListener(CallEvent.EVENT_CALL_FAIL, 		callFailHandler);
		}
		
		// -------------------------------------------------------------------------------
		// PROPERTIES
		// -------------------------------------------------------------------------------
		
		public function get count():uint
		{
			return _cards.length;
		}
		
		/**
		 * Checks if the CardManager is loading cards 
		 * @return true if the card manager is loading data
		 */
		public function get isLoading():Boolean
		{
			return _call.executing;
		}
		
		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		// Handling
		
		protected function indexOfById(cardId:uint):int
		{
			var i:int = Shared.INVALID_INDEX;
			for (i=0; i<_cards.length; i++) if (_cards[i].cardId == cardId) break;
			return i;
		}
		
		/**
		 * Starts the cards data loading 
		 */		
		protected function startLoad():void
		{
			// Dispatch CardManager.EVENT_LOAD_STARTED event
			_instance.dispatchLoadStarted();
			// Executes the loading call
			_call.execute();
		}
		
		/**
		 * If loading, stops the load 
		 */		
		protected function cancelLoad():void
		{
			if (_call.executing) _call.cancel();
			// Dispatch CardManager.EVENT_LOAD_CANCELLED event
			_instance.dispatchLoadCancelled();
		}
		
		/**
		 * Parses the JSON data representing the card list 
		 * @param data JSON data representing the card list
		 */		
		protected function parse(data:Object):void
		{
			// Locals
			var cards:Vector.<Card> = new Vector.<Card>();
			// safe exec
			try
			{
				// Validate data
				if (!data.hasOwnProperty(JSON_PROPERTY_CARD_LIST)) throw new Error("CardManager.parse() failed: object provided doesn't contains '" + JSON_PROPERTY_CARD_LIST + "' field!");
				// Parse data
				var raw_cards:Array = data[JSON_PROPERTY_CARD_LIST]; 
				// Creates the card list
				for (var i:int=0; i<raw_cards.length; i++)
				{
					// Create card without instance id
					cards.push(CardFactory.createByKind(CardKind.DEFINITION, raw_cards[i]));
				}
				// Clears and fills the card list
				_cards.splice(0, _cards.length); 
				for (var j:int=0; j<cards.length; j++)
				{
					_cards.push(cards[j]);
				}
			}
			catch (err:Error)
			{
				// DEBUG > Unexpected error
				Logger.error("CardManager.parse() unexpected error", err);
				// Dispatch CardManager.EVENT_LOAD_FAILED event 
				throw new Error("CardManager.parse() unexpected error: " + err.message + " - data: " + JsonUtils.encode(data));
			}
		}
		
		// Call handlers
		
		private function callSuccesHandler(e:CallSuccessEvent):void
		{
			try
			{
				// Parse data
				parse(e.call.data);
				// Dispatch CardManager.EVENT_LOAD_COMPLETED event 
				dispatchLoadCompleted();
			}
			catch (err:Error)
			{
				// DEBUG > Unexpected error
				Logger.error("CardManager.callSuccesHandler() unexpected error", err);
				// Dispatch CardManager.EVENT_LOAD_FAILED event 
				dispatchLoadFailed("CardManager.callSuccesHandler() unexpected error: " + err.message);
			}
		}
		
		private function callErrorHandler(e:CallErrorEvent):void
		{
			try
			{
				// Dispatch CardManager.EVENT_LOAD_FAILED event 
				dispatchLoadFailed("CardManager data loading failed: " + e.call.error.message + "(type: " + e.call.error.type + ", code: " + e.call.error.code + ")");
			}
			catch (err:Error)
			{
				// DEBUG > Unexpected error
				Logger.error("CardManager.callErrorHandler() unexpected error", err);
				// Dispatch CardManager.EVENT_LOAD_FAILED event 
				dispatchLoadFailed("CardManager.callErrorHandler() unexpected error: " + err.message);
			}			
		}
		
		private function callFailHandler(e:CallFailEvent):void
		{
			try
			{
				// Dispatch CardManager.EVENT_LOAD_FAILED event 
				dispatchLoadFailed(e.message);				
			}
			catch (err:Error)
			{
				// DEBUG > Unexpected error
				Logger.error("CardManager.callFailHandler() unexpected error", err);
				// Dispatch CardManager.EVENT_LOAD_FAILED event 
				dispatchLoadFailed("CardManager.callErrorHandler() unexpected error: " + err.message);
			}
		}
		
		// Events dispatcher

		/**
		 * Dispatch CardManager.EVENT_LOAD_STARTED event 
		 */
		protected function dispatchLoadStarted():void
		{
			dispatchEvent(new CardManagerEvent(CardManagerEvent.EVENT_LOAD_STARTED, this));
		}

		/**
		 * Dispatch CardManager.EVENT_LOAD_CANCELLED event 
		 */
		protected function dispatchLoadCancelled():void
		{
			dispatchEvent(new CardManagerEvent(CardManagerEvent.EVENT_LOAD_CANCELLED, this, "Card data loading cancelled by the user"));
		}
		
		/**
		 * Dispatch CardManager.EVENT_LOAD_COMPLETED event 
		 */		
		protected function dispatchLoadCompleted():void
		{
			dispatchEvent(new CardManagerEvent(CardManagerEvent.EVENT_LOAD_COMPLETED, this));
		}
		
		/**
		 * Dispatch CardManager.EVENT_LOAD_FAILED event 
		 */
		protected function dispatchLoadFailed(errorMessage:String):void
		{
			dispatchEvent(new CardManagerEvent(CardManagerEvent.EVENT_LOAD_FAILED, this, errorMessage));
		}		
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		// 
		
		/**
		 * Check if a card for an id exists
		 * @return  
		 */		
		public function hasCardById(id:uint):Boolean
		{
			return indexOfById(id) != Shared.INVALID_INDEX;
		}
		
		/**
		 * 
		 * @param index
		 * @return  
		 */		
		public function getCard(index:int):Card
		{
			return _cards[index];
		}
		
		/**
		 * 
		 * @param index
		 * @return  
		 */		
		public function getCardById(id:uint):Card
		{
			var card:Card;
			var index:int = indexOfById(id);
			if (index != Shared.INVALID_INDEX) card = _cards[index];
			return card;
		}
		
		/**
		 * 
		 * @param index
		 * @return  
		 */		
		public function getActionCard(index:int):ActionCard
		{
			var card:ActionCard = getCard(index) as ActionCard;
			if (card == null) throw new Error("Card for index " + index + " is not a valid ActionCard");
			return card;
		}
		
		/**
		 * 
		 * @param id
		 * @return  
		 */		
		public function getActionCardById(id:uint):ActionCard
		{
			var card:ActionCard = getCardById(id) as ActionCard;
			if (card == null) throw new Error("Card for id " + id + " is not a valid ActionCard");
			return card;
		}
		
		/**
		 * 
		 * @param index
		 * @return  
		 */		
		public function getPlayerCard(index:int):PlayerCard
		{
			var card:PlayerCard = getCard(index) as PlayerCard;
			if (card == null) throw new Error("Card for index " + index + " is not a valid ActionCard");
			return card;
		}
		
		/**
		 * 
		 * @param id
		 * @return  
		 */		
		public function getPlayerCardById(id:uint):PlayerCard
		{
			var card:PlayerCard = getCardById(id) as PlayerCard;
			if (card == null) throw new Error("Card for id " + id + " is not a valid ActionCard");
			return card;
		}
		
		/**
		 * Creates a new card of CardKind.INSTANCE using a Card definition contained into the manager.
		 * If some data is provided, updates the newly created using the basic Card.update method to fullfill
		 * new properties
		 * @param id Id of Che Card definition to use to create the card instance
		 * @param instanceId Instance id of the Card to create from the definition
		 * @param data Data to use to update the new instance properties
		 * @return A new instance that representes a card instance for the player
		 */				
		public function createCardInstance(id:uint, instanceId:uint, data:Object = null):Card
		{
			// Retrieve the original card
			var sourceCard:Card = getCardById(id);
			var instancedCard:Card = getCardById(id);
			// Check if the card exists
			if (sourceCard != null)
			{
				// Clones the card
				instancedCard = sourceCard.duplicate(CardKind.INSTANCE, instanceId);
				// Updates card with the provided data
				// LT_TODO: aggiungere il metodo update() a tutti gli oggetti Card, in modo da fare una gestione
				// centralizzata del caricamento opzionale dei dati parallelo al metodo load() che valida i dati necessari
				// newCard.update(data);
			}
			else throw new Error("Card with id " + id + " not found!");
			// Returns the instanced card
			return instancedCard;
		}
		
		/**
		 * Loads all the card data into the CardManager from the server  
		 */		
		public function load():void
		{
			// Starts the loading
			_instance.startLoad();
		}
		
		// IEventDispatcher implementation
		
		public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
		{
			_dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
		
		public function dispatchEvent(event:Event):Boolean
		{
			return _dispatcher.dispatchEvent(event);
		}
		
		public function hasEventListener(type:String):Boolean
		{
			return _dispatcher.hasEventListener(type);
		}
		
		public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void
		{
			_dispatcher.removeEventListener(type, listener, useCapture);
		}
		
		public function willTrigger(type:String):Boolean
		{
			return _dispatcher.willTrigger(type);
		}
		

	}
}